import numpy as np
from sklearn.datasets import load_digits  # 从sklearn数据集中加载手写数字图片
from sklearn.model_selection import train_test_split  # 分割训练集和测试集
from sklearn.preprocessing import LabelBinarizer  # 对带有标签图片进行处理
import matplotlib.pyplot as plt

# 载入数据（手写数字图片）
digits = load_digits()
print(digits.images.shape)  # 打印数据集的形状

# 显示手写数字图片
# plt.imshow(digits.images[1], cmap='gray')
# plt.show()
plt.imshow(digits.images[25], cmap='gray')
plt.show()

# 读入样本集的数据
X = digits.data
# 读入样本集的标签
y = digits.target
# print(X.shape)
# print(y.shape)
# print(X[:3])  # 打印样本集中前4个样本的数据0-3
# print(y[:3])  # 打印样本集中前4个样本的标签0-3

# 定义一个神经网络，网络结构——输入层：64个神经元；隐藏层：100个；输出层：10个神经元（10个输出值）
# 定义输入层到隐藏层的权值矩阵
V = np.random.random((64, 100)) * 2 - 1  # 64*100;random随机生成数字0-1；*2-1：从-1~1之间
# 定义隐藏层到输出层的权值矩阵
W = np.random.random((100, 10)) * 2 - 1  # size:100*10

# 数据切分
# 一般将样本数据划分成：1/4为测试集（用来测试模型效果），3/4为训练集
X_train, X_test, y_train, y_test = train_test_split(X, y)

# 标签二值化 （这步操作是把某个具体标签值转换成其在所有标签中的位置，方便输出）
# 0—>1000000000
# 3->0001000000
# 9->0000000001
labels_train = LabelBinarizer().fit_transform(y_train)  # 把原本的标签训练集二值化

# print(y_train[:5])
# print(labels_train[:5])

# 定义激活函数
def sigmod(x):
    return 1 / (1 + np.exp(-x))

# 定义激活函数的导数
def dsigmod(x):
    return x * (1 - x)

# 定义训练模型
def train(X, y, steps, lr=0.11):  # 训练集、标签集、训练次数、学习率
    global V, W  # 调用全局变量
    for n in range(steps + 1):
        # 随机选取一个样本中的数据
        i = np.random.randint(X.shape[0])  # 取第一维数据，即训练样本中的第一个
        x = X[i]  # 取样本中的具体数值（64个）
        x = np.atleast_2d(x)  # 把一维数据表转成2维，方便与权值矩阵做乘法

        # BP算法公式
        # 计算隐藏层的输出
        L1 = sigmod(np.dot(x, V))
        # 计算输出层的输出
        L2 = sigmod(np.dot(L1, W))

        # 计算L2_delta、 L1_delta（通多这两个值来更新权重）
        L2_delta = (y[i] - L2) * dsigmod(L2)  # 对应公式：输出层学习信号
        L1_delta = L2_delta.dot(W.T) * dsigmod(L1)  # 对应公式：第l层学习信号

        # 更新权值
        W += lr * L1.T.dot(L2_delta)
        V += lr * x.T.dot(L1_delta)

        # 每训练1000次可以预测一次准确率
        if n % 1000 == 0:
            print("steps:", n, "accuracy:", test())

# 该函数输入测试集，输出标签值
def predict(x):
    # 计算隐藏层的输出
    L1 = sigmod(np.dot(x, V))
    # 计算输出层的输出
    L2 = sigmod(np.dot(L1, W))
    return L2

def test():
    output = predict(X_test)  # 把测试集传入测试模型
    predictions = np.argmax(output, axis=1)  # 获取最大值的位置，实际上得到标签值
    # 对比网络预测值和真实的标签值是否相等，相等为TURE（1）,不等为false（0）,求平均，即为准确率
    acc = np.mean(np.equal(predictions, y_test))
    return acc

print('每训练1000次可以预测一次准确率')
train(X_train, labels_train, 20000)  # 调用BP算法训练模型

for i in range(10):
    print('测试样本为：', y_test[i], '预测结果为：', np.argmax(predict(X_test[i])))
